#!/usr/bin/env python

# Python Standard Library
import codeop
import doctest
import glob
import os
import shutil
import platform
import re
import sys
import tempfile

# Third-Party Libraries
import strictyaml

# Test Files
# ------------------------------------------------------------------------------
mkdocs_nav = strictyaml.load(open("mkdocs.yml").read())["nav"].data
test_files = ["mkdocs/" + list(item.values())[0] for item in mkdocs_nav]

# Sandbox the Test Files
# ------------------------------------------------------------------------------
# This is required:
#   - to tweak the files before the tests,
#   - to avoid the generation of artifacts (generated by the test code)
#     in the current directory.
tmp_dir = tempfile.mkdtemp()  # TODO: clean-up this directory
for filename in test_files:
    target_file = os.path.join(tmp_dir, filename)
    target_dir = os.path.dirname(target_file)
    os.makedirs(target_dir, exist_ok=True)
    shutil.copy(filename, target_file)

# Tweak the Test Files
# ------------------------------------------------------------------------------
# For each file, find the python fences, see if they are in interpreter mode
# or "code" mode. If they are in code mode, add the prompts then remove the
# fences and indent the code lines.
def promptize(src):
    "Add >>> or ... prompts to Python code"
    cc = codeop.compile_command  # symbol="single" (the default here)
    # is required to deal with if / else constructs properly
    # (without going back to the ">>> " prompt after the if clause).
    lines = src.splitlines()
    output = []
    chunk = []
    for line in lines:
        if chunk == []:  # new start
            output.append(">>> " + line)
        else:
            output.append("... " + line)
        chunk.append(line)
        try:
            code = cc("\n".join(chunk))
            if code is not None:  # full statement
                chunk = []  # start over
        except:  # pragma: no cover
            raise
    assert len(lines) == len(output)
    return "\n".join(output)


def tweak(src):
    # Find code blocks with python fences,
    # add prompts when necessary,
    # then transform them into indented code blocks.
    lines = src.splitlines()
    chunks = {}
    python, sep, start, end, code = False, None, None, None, []
    for i, line in enumerate(lines):
        if (  # match at least three backquotes, optional space, then python or pycon
            re.match(r"\s*(`|~){3,}\s*py(c|th)on", line)
        ):
            sep = line.strip()[0]
            assert sep in "`~"
            start = i
            code.append("")
            python = True
        elif python is True and 3 * sep in line:
            sep = None
            end = i + 1
            code.append("")
            python = False
            assert end - start == len(code)
            chunks[(start, end)] = code
            code = []
        elif code != []:
            code.append(line)

    for loc, code in chunks.items():
        chunk = "\n".join(code[1:-1])  # dont promptize initial and final newline
        if not chunk.strip().startswith(">>> "):  # prompts are missing
            code[1:-1] = promptize(chunk).splitlines()
        code = [4 * " " + line for line in code]
        chunks[loc] = code

    for (i, j), code in chunks.items():
        lines[i:j] = code
    new_src = "\n".join(lines)
    return new_src


cwd = os.getcwd()
os.chdir(tmp_dir)

for filename in test_files:
    with open(filename, encoding="utf-8") as file:
        src = file.read()
    if filename == "mkdocs/markdown.md":

        src = "``` python\nimport pandoc\n```\n\n" + src

        # Need to fetch markdown content, unident, wrap in ``` python block
        # and add the text = """ stuff.
        # Then add a ``` pycon block with repr stuff

        pattern = r'=== "Markdown"(?:(?:\n)|(?:[ ]{8}.*\n))*'
        pattern += r'=== "Python"(?:(?:\n)|(?:[ ]{8}.*\n))*'

        found = re.findall(pattern, src)
        ritems = []
        for item in found:

            if "```" in item:
                sep = "~~~"
            else:
                sep = "```"

            ritem = item.strip()
            ritem = re.sub("^[ ]{8}", "", ritem, flags=re.MULTILINE)
            ritem = ritem.replace(
                '=== "Markdown"\n\n', f'=== "Markdown"\n\n{sep} python\ntext = \\\nr"""'
            )
            ritem = ritem.replace(
                '\n=== "Python"\n',
                f'"""\n{sep}\n\n=== "Python"\n\n{sep} pycon\n>>> pandoc.read(text)',
            )
            ritem += f"\n{sep}\n\n"
            ritems.append(ritem)

        for item, ritem in zip(found, ritems):
            src = src.replace(item, ritem)

        # and in any case, "normal tweak"
    src = tweak(src)

    with open(filename, "w", encoding="utf-8") as file:
        file.write(src)

# Run the Tests
# ------------------------------------------------------------------------------
verbose = "-v" in sys.argv or "--verbose" in sys.argv

fails = 0
tests = 0
for filename in test_files:
    options = {"module_relative": False, "verbose": verbose}

    # Relax the tests to deal with test files that have a '\n' line break
    # (Linux flavor) which does not match the pandoc line break on Windows
    # (Windows flavor : '\r\n').
    # The proper way to deal with this would be to convert the test files
    # beforehand on Windows.
    if platform.system() == "Windows":
        options["optionflags"] = doctest.NORMALIZE_WHITESPACE

    _fails, _tests = doctest.testfile(filename, **options)
    fails += _fails
    tests += _tests

os.chdir(cwd)

if fails > 0 or verbose:  # pragma: no cover
    print()
    print(60 * "-")
    print("Test Suite Report:", end=" ")
    print("{0} failures / {1} tests".format(fails, tests))
    print(60 * "-")
if fails:  # pragma: no cover
    sys.exit(1)
